package com.cpedia.android.deal.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;


/**
 * User: pchen
 * Date: Jun 4, 2009
 */


public class ServerConnection {

    private static final String POST = "POST";
    private static final String GET = "GET";
    private static Logger logger = Logger.getLogger(ServerConnection.class.getName());
    private android.webkit.CookieManager cookies;


    public ServerConnection() {
        cookies = android.webkit.CookieManager.getInstance();
    }

    public void saveCookies() {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            (new ObjectOutputStream(out)).writeObject(cookies);
            byte bytes[] = out.toByteArray();
/*
            Preferences prefs = Preferences.userNodeForPackage(com / google / appengine / tools / admin / ServerConnection);
            prefs.put("email", options.getUserId());
            prefs.putByteArray("cookies", bytes);
*/
        }
        catch (IOException ex) {
        }
        catch (IllegalArgumentException ex) {
        }
    }

    public String post(String url, String payload, String params[])
            throws IOException {
        Map paramMap = new HashMap();
        for (int i = 0; i < params.length; i += 2)
            paramMap.put(params[i], params[i + 1]);

        return send("POST", url, new StringPoster(payload), null, paramMap);
    }

    public String post(String url, String payload, Map params)
            throws IOException {
        return send("POST", url, new StringPoster(payload), null, params);
    }


    public String get(String url, Map params)
            throws IOException {
        return send("GET", url, null, null, params);
    }

    private String send(String method, String path, DataPoster payload, String content_type, Map params)
            throws IOException, ClientLoginException {
        URL url = new URL((new StringBuilder()).append("http://")./*append(getServer()).*/append(path).append('?').append(buildQuery(params)).toString());
        if (content_type == null)
            content_type = "application/octet-stream";
        int tries = 0;
        do {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestProperty("Content-type", content_type);
            conn.setRequestProperty("X-appcfg-api-version", "1");
/*
            if (options.getHost() != null)
                conn.setRequestProperty("Host", options.getHost());
*/
            IOException ioe = connect(method, conn, payload);
            int status = conn.getResponseCode();
            BufferedReader reader = getReader(conn);
            if (status == 200) {
                StringBuffer response = new StringBuffer();
                for (String line = null; (line = reader.readLine()) != null;) {
                    response.append(line);
                    response.append("\n");
                }

                return response.toString();
            }
            logger.finer((new StringBuilder()).append("Got http error ").append(status).append(". this is try #").append(tries).toString());
            if (++tries > 3)
                throw new IOException(constructHttpErrorMessage(conn, reader));
            if (status == 401)
                authenticate(url.getHost(), null);
            else if ((status < 500 || status > 600) && status == 302) {
                Map headers = conn.getHeaderFields();
                String location = (String) ((List) headers.get("Location")).get(0);
                if (location.startsWith("https://www.google.com/accounts/ServiceLogin"))
                    authenticate(url.getHost(), null);
                else if (location.matches("https://www.google.com/a/[a-z0-9.-]+/ServiceLogin"))
                    authenticate(url.getHost(), "HOSTED");
            }
        } while (true);
    }

    private String constructHttpErrorMessage(HttpURLConnection conn, BufferedReader reader)
            throws IOException {
        StringBuilder sb = new StringBuilder("Error posting to URL: ");
        sb.append(conn.getURL());
        sb.append('\n');
        sb.append(conn.getResponseCode());
        sb.append(' ');
        sb.append(conn.getResponseMessage());
        sb.append('\n');
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
            sb.append('\n');
        }
        return sb.toString();
    }


    private String buildQuery(Map params)
            throws UnsupportedEncodingException {
        StringBuffer buf = new StringBuffer();
        for (Iterator i$ = params.keySet().iterator(); i$.hasNext(); buf.append('&')) {
            String key = (String) i$.next();
            buf.append(URLEncoder.encode(key, "UTF-8"));
            buf.append('=');
            buf.append(URLEncoder.encode((String) params.get(key), "UTF-8"));
        }

        return buf.toString();
    }

    private void authenticate(String host, String account_type)
            throws ClientLoginException, IOException {
        int unused = 1;
        do {
            try {
                String authToken = getAuthToken(host, account_type);
                getAuthCookie(authToken);
                return;
            }
            catch (ClientLoginException e) {
                if (unused >= 3)
                    throw e;
            }
            unused++;
        } while (true);
    }

    private String getAuthToken(String host, String accountType)
            throws IOException, ClientLoginException {
        if (accountType == null)
            if (host.endsWith(".google.com"))
                accountType = "HOSTED_OR_GOOGLE";
            else
                accountType = "GOOGLE";
        URL url = new URL("https://www.google.com/accounts/ClientLogin");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        Map params = new HashMap();
        // params.put("Email", options.getUserId());
        // params.put("Passwd", password);
        params.put("service", "ah");
        params.put("source", "Google-appcfg-java-unknown");
        params.put("accountType", accountType);
        IOException ioe = connect("POST", conn, new StringPoster(buildQuery(params)));
        BufferedReader reader = getReader(conn);
        HashMap response = new HashMap();
        String line = null;
        do {
            if ((line = reader.readLine()) == null)
                break;
            String pair[] = line.split("=", 2);
            if (pair.length == 2)
                response.put(pair[0], pair[1]);
        } while (true);
        if (conn.getResponseCode() == 200)
            return (String) response.get("Auth");
        if (conn.getResponseCode() == 403) {
            String reason = (String) response.get("Error");
            if ("BadAuthentication".equals(reason))
                throw new ClientLoginException((new StringBuilder()).append("Email \"").append(/*options.getUserId()*/"").append("\" and password do not match.").toString(), ioe);
            if ("CaptchaRequired".equals(reason))
                throw new ClientLoginException("Please go to https://www.google.com/accounts/DisplayUnlockCaptcha and verify you are a human. Then try again.");
            if ("NotVerified".equals(reason))
                throw new ClientLoginException("Your account has not yet been verfied. Please check your email to do that, then try again.");
            if ("TermsNotAgreed".equals(reason))
                throw new ClientLoginException("You have not yet agreed to the Terms of Service on your account. Please do that, then try again.");
            if ("AccountDeleted".equals(reason))
                throw new ClientLoginException("Your user account has been deleted. If this is an error, contact account support at http://www.google.com/support/accounts/");
            if ("AccountDisabled".equals(reason))
                throw new ClientLoginException("Your user account has been disabled. If this is an error, contact account support at http://www.google.com/support/accounts/");
            if ("ServiceUnavailable".equals(reason))
                throw new ClientLoginException("The service is currently unavailable; try again later.");
            else
                throw new ClientLoginException((String) response.get("Error"), ioe);
        }
        if (conn.getResponseCode() == 401)
            throw new ClientAuthFailException((new StringBuilder()).append("Email \"")./*append(options.getUserId()).*/append("\" and password do not match.").toString(), ioe);
        else
            throw new RuntimeException((new StringBuilder()).append("Bad authentication response: ").append(conn.getResponseCode()).append(" ").append(conn.getResponseMessage()).toString(), ioe);
    }

    private void getAuthCookie(String token)
            throws IOException {
        Map params = new HashMap();
        params.put("continue", "http://localhost/");
        params.put("auth", token);
        String query = buildQuery(params);
        URL url = new URL((new StringBuilder()).append("http://")/*.append(getServer())*/.append("/_ah/login?").append(query).toString());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        IOException ioe = connect("POST", conn, null);
        if (conn.getResponseCode() != 302 || !"http://localhost/".equals(conn.getHeaderField("Location")))
            throw new RuntimeException((new StringBuilder()).append("Bad authentication response: ").append(conn.getResponseCode()).append(" ").append(conn.getResponseMessage()).toString());
        if (ioe != null)
            throw ioe;
        else
            return;
    }

    private IOException connect(String method, HttpURLConnection conn, DataPoster data)
            throws IOException {
        //cookies.writeCookies(conn);
        conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod(method);
        if ("POST".equals(method)) {
            conn.setDoOutput(true);
            OutputStream out = conn.getOutputStream();
            if (data != null)
                data.post(out);
            out.close();
        }
        //cookies.readCookies(conn);
        try {
            conn.getInputStream();
        }
        catch (IOException ex) {
            return ex;
        }
        return null;
    }

    private BufferedReader getReader(HttpURLConnection conn) {
        InputStream is;
        try {
            is = conn.getInputStream();
        }
        catch (IOException ex) {
            is = conn.getErrorStream();
        }
        if (is == null) {
            return null;
        } else {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            return reader;
        }
    }


    private static interface DataPoster {

        public abstract void post(OutputStream outputstream)
                throws IOException;
    }

    private class StringPoster implements DataPoster {

        public void post(OutputStream s)
                throws IOException {
            s.write(str.getBytes("UTF-8"));
        }

        private String str;
        final ServerConnection serverConnection;

        public StringPoster(String s) {
            serverConnection = ServerConnection.this;
            str = s;
        }
    }

    public class ClientAuthFailException extends ClientLoginException {
        public ClientAuthFailException(String s) {
            super(s);
        }

        public ClientAuthFailException(String s, Throwable c) {
            super(s, c);
        }
    }

    public class ClientLoginException extends IOException {


        public ClientLoginException(String s) {
            super(s);
        }

        public ClientLoginException(String s, Throwable c) {
            super(s);
            initCause(c);
        }
    }
}